package ArithmeticLibrary.Elements;

import java.math.BigInteger;

import ArithmeticLibrary.Sets.Group;
import ArithmeticLibrary.Sets.MultiplicativeGroup;
import ByteTree.ByteTree;
import ByteTree.Leaf;
import ByteTree.Node;

/**
 * This class represents a multiplicative group element
 * This class supports group operations: multiplication, division, power, and multiplicative inverse.
 * 
 * @author Lea Golan
 *
 */
public class ElementMultiplicativeGroup extends ElementGroup {

	private MultiplicativeGroup set;//a reference to the set the element belongs to
	private BigInteger val;//the numeric value of the element
	
	/**
	 * constructs a new element according to the given parameters
	 * 
	 * @param element - the element value
	 * @param containerGroup - the group the element belongs to
	 */
	public ElementMultiplicativeGroup(java.math.BigInteger element, 
										MultiplicativeGroup containerGroup) {
		this.set = containerGroup;
		this.val = this.set.getPClass().getValModulo(element);
	}
	
	/**
	 * constructs a new element according to the given parameters
	 * 
	 * @param message - is of the form: leaf(bytes_k (a)) where 
	 * 		a is the value of the element, 
	 * 		k is the smallest integer bigger than the logarithm in base two of the prime group order
	 * @param containerGroup - the group the element belongs to
	 * @throws Exception if message is not of the described structure
	 */
	public ElementMultiplicativeGroup(ByteTree message,
			MultiplicativeGroup containerGroup) throws Exception {
		this.set = containerGroup;
		try {
			Node root = message.getRoot();
			if (root.isLeaf() == false){
				throw new Exception("ByteTree structure is inconsistent with ElementMultiplicativeGroup");
			}
			BigInteger temp = new BigInteger(((Leaf) root).getData());
			this.val = this.set.getPClass().getValModulo(temp);
		} catch (Exception e) {
			throw new Exception("Build Error in ElementMultiplicativeGroup:\n"+e.getMessage());
		}
	}

	/**
	 * Compare this to obj by checking equivalence of the value and the set.
	 */
	public boolean equals(java.lang.Object object){
		if (object == this){
            return true;
        }
        if (!(object instanceof ElementMultiplicativeGroup)){
            return false;
        }
        
        ElementMultiplicativeGroup other = (ElementMultiplicativeGroup) object;
        return ( (this.val.equals(other.val)) && (this.set.equals(other.set)));
	}
	
	/**
	 * Checks if this structure is consistent with obj structure. 
	 * it returns true iff obj is an instance of the same class. 
	 * 
	 * @param obj an object we want to check with our structure
	 * @return true iff this and obj are of the same structure
	 */
	@Override
	public boolean checkStructure(Object obj) {
		return (obj instanceof ElementMultiplicativeGroup);
	}

	/**
	 * @return the element's representation as a number in the group
	 */
	public java.math.BigInteger getElementValue(){
		return this.val;
	}

	/**
	 * sets val to newVal
	 * 
	 * @param newVal - a new numeric value of the group the element belongs to
	 */
	public void setElementValue(java.math.BigInteger newVal){
		this.val = this.set.getPClass().getValModulo(newVal);
	}
	
	/**
	 * @return the group which the element belongs to
	 */
	@Override
	public Group getGroup() {
		return this.set;
	}	
	
	/**
	 * Returns a new group element (can be a product or a simple element)
	 * which is the result of calculating this^exponent.
	 * 
	 * @param exponent - an exponent
	 * @return a new ElementGroup that holds the value this^exponent
	 */
	@Override
	public ElementMultiplicativeGroup power(BigInteger exponent) {		
		BigInteger result = this.set.getPClass().powModulo(val, exponent);
		return new ElementMultiplicativeGroup(result, set);
	}
	
	/**
	 * Returns a new group element which is the result of dividing this element by the other element 
	 * both elements must be in the same group. 
	 * 
	 * @param other - the element we want to divide by
	 * @return a new element that is the result of dividing this to other 
	 * @throws Exception if the mathematical operation fails
	 */
	@Override
	public ElementMultiplicativeGroup divide(ElementGroup other) throws Exception {
		BigInteger result;
		try {
			result = this.set.getPClass().divideModulo(val, checkElement(other));
		} catch (Exception e) {
			throw new Exception("error in ElementMultiplicativeGroup.divide:\n"+e.getMessage());
		}
		return new ElementMultiplicativeGroup(result, set);
	}
	
	/**
	 * multiplies this by the value other holds
	 * 
	 * @param other - the element we multiply by
	 * @return a new group element which is the result of multiplying the current element with the other element.
	 * @throws Exception if the mathematical operation fails
	 */
	@Override
	public ElementMultiplicativeGroup multiply(ElementGroup other) throws Exception {
		BigInteger result;
		try {
			result = this.set.getPClass().multiplyModulo(val, checkElement(other));
		} catch (Exception e) {
			throw new Exception("error in ElementMultiplicativeGroup.multiply:\n"+e.getMessage());
		}
		return new ElementMultiplicativeGroup(result, set);
	}
	
	/**
	 * performs inverse operation on the element value with respect to multiplication
	 * 
	 * @return a new group element which is the multiplicative inverse of this
	 * @throws Exception if the mathematical operation fails
	 */
	@Override
	public ElementMultiplicativeGroup multInverse() throws Exception {
		try {
			return new ElementMultiplicativeGroup(set.getPClass().multInverseModulo(val), set);
		} catch (Exception e) {
			throw new Exception("error in ElementMultiplicativeGroup.multInverse:\n"+e.getMessage());
		}
	}
	
	private java.math.BigInteger checkElement(ElementGroup other) throws Exception{
		if (!(other instanceof ElementMultiplicativeGroup)){
			throw new Exception("Element must be a instance of ElementMultiplicativeGroup");
		}
		ElementMultiplicativeGroup otherElem = (ElementMultiplicativeGroup) other;
		if ((this.set.equals(otherElem.set)) == false) {
			throw new Exception("Elements must belong to the same group");
		}
		return otherElem.val;
	}

}
